/*
 * Copyright (C) 2025 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package top.xbaistack.ws;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component;
import top.xbaistack.common.MessageConsumer;
import top.xbaistack.common.WebSocketRequest;
import top.xbaistack.common.WebSocketResponse;
import top.xbaistack.config.JsonMapper;
import top.xbaistack.exception.BaseException;
import top.xbaistack.game.Player;
import top.xbaistack.game.Room;
import top.xbaistack.utils.Assert;
import top.xbaistack.utils.WebSocket;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * WebSocket 端点，用于对外提供 WebSocket 服务。地址：ws://localhost:8888/room/2/3
 *
 * @author xbaistack
 * @since 2025/03/25
 */
@Slf4j
@Component
@ServerEndpoint("/room/{room}/{player}")
public class RoomEndpoint {

    @OnOpen
    public void onOpen(Session session, @PathParam("room") String room, @PathParam("player") String player) {
        System.out.println("Socket.onOpen: room -> " + room + ", player -> " + player);
    }

    @OnMessage
    public void onMessage(
            Session session,
            @PathParam("room") String room,
            @PathParam("player") String player,
            String message
    ) {
        System.out.println("Socket.onMessage: room -> " + room + ", player -> " + player);
        System.out.println(message);

        // 统一将请求数据转换成对象（前端必须传递 JSON 格式的数据才行）
        WebSocketRequest request = JsonMapper.fromJson(message, WebSocketRequest.class);
        if (request == null) {
            WebSocket.sendMessage(session, WebSocketResponse.error("请求消息格式错误！"));
            return;
        }

        // 消息类型检测
        String msgType = request.getType();
        if (!consumers.containsKey(msgType)) {
            WebSocket.sendMessage(session, WebSocketResponse.error("不支持的消息类型：" + msgType));
            return;
        }

        // 使用对应的消息请求函数去处理相应的消息内容
        request.setRoomId(room);
        request.setPlayerId(player);
        MessageConsumer consumer = consumers.get(msgType);
        consumer.accept(session, request);
    }

    @OnClose
    public void onClose(Session session, @PathParam("room") String room, @PathParam("player") String player) {
        System.out.println("Socket.onClose: room -> " + room + ", player -> " + player);
    }

    @OnError
    public void onError(
            Session session,
            @PathParam("room") String room,
            @PathParam("player") String player,
            Throwable error
    ) {
        System.out.println("Socket.onError " + error);
        final String message = error instanceof BaseException ? error.getMessage() : "服务异常！";
        WebSocket.sendMessage(session, WebSocketResponse.error(message));
        RoomManager.offline(room, player);
    }

    private static final Map<String, MessageConsumer> consumers = new HashMap<>(16);

    static {
        consumers.put("ping", RoomEndpoint::ping);
        consumers.put("register", RoomEndpoint::register);
        consumers.put("move", RoomEndpoint::actionMove);
        consumers.put("exit", RoomEndpoint::exit);
    }

    private static void ping(Session session, WebSocketRequest request) {
        RoomManager.getRoom(request.getRoomId());
        WebSocket.sendMessage(session, new WebSocketResponse("ping", "pong"));
    }

    private static void register(Session session, WebSocketRequest request) {
        RoomManager.join(session, request.getRoomId(), request.getPlayerId());
    }

    private static void actionMove(Session session, WebSocketRequest request) {
        Room room = RoomManager.getRoom(request.getRoomId());
        Player player = room.getPlayer(request.getPlayerId());
        Assert.notNull(player, "玩家信息不存在！");

        // 落子
        int[] axis = request.dataOf(int[].class);
        room.moveTo(player.getRole(), axis[ 0 ], axis[ 1 ]);

        // 每一次落子都需要判断是否五颗连成一条线，如果达成条件，则游戏结束。
        if (room.isFinished(player.getRole(), axis[ 0 ], axis[ 1 ])) {
            room.setOver(true);
            player.setWinner(true);
            player.sendMessage(WebSocketResponse.finished("我赢啦~"));
            room.exclude(player.getId(), p -> {
                p.setWinner(false);
                p.sendMessage(WebSocketResponse.finished("我败了！"));
            });
            return;
        }

        // 当前用户走后冻结棋盘
        player.setFrozen(true);

        // 推送消息给其他玩家同步棋盘信息
        List<Player> players = room.getPlayers();
        if (CollectionUtils.isNotEmpty(players)) {

            // 落子后刷新所有玩家的棋盘
            // 使用最新的棋盘数据去刷新棋局，防止用于在前端页面上作弊修改。
            players.forEach(p -> p.sendMessage(WebSocketResponse.refresh(room)));

            // 由于用户落子后会冻结棋盘，因此无论谁下了子，都需要给对方棋盘解冻。
            room.exclude(player.getId(), p -> {
                p.setFrozen(false);
                p.sendMessage(WebSocketResponse.unfreeze());
            });
        }
    }

    private static void exit(Session session, WebSocketRequest request) {
        RoomManager.exit(request.getRoomId());
    }
}